<!DOCTYPE html><html class="appearance-auto" lang="zh-CN"><head><meta charset="UTF-8"><title>【SpringBoot源码解析系列】 Jar启动实现</title><meta name="description" content="用创业者心态做好每一件事情！"><meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, initial-scale=1"><!-- Google Analytics --><!-- End Google Analytics -->
<!-- Baidu Analytics --><!-- End Baidu Analytics --><link rel="icon" href="/blog/images/favicon.png"><link rel="stylesheet" href="/blog/style/common/bulma.css"><link rel="stylesheet" href="/blog/style/base.css"><link rel="stylesheet" href="/blog/style/common/helper.css"><script src="/blog/js/common.js"></script><link rel="stylesheet" href="/blog/style/post.css"><link rel="stylesheet" href="/blog/style/themes/highlight-theme-light.css"><script src="/blog/js/highlight.pack.js"></script><meta name="description" content="
本文主要讲述SpringBoot中Jar启动实现


一、概述Spring Boot 提供了 Maven 插件 spring-boot-maven-plugin，可以方便的将 Spring Boot 项目打成 jar包或者 war 包。Jar包结构图，一共分为4个部分
① META-INF 目录：通过 MANIFEST.MF 文件提供 jar 包的元数据，声明了 jar 的启动类。② org 目录：为 Spring Boot 提供的 spring-boot-loader 项目，它是 java -jar 启动 Spring Boot 项目的秘密所在，也是稍后我们将深入了解的部分。③ BOOT-INF/lib 目录：我们 Spring Boot 项目中引入的依赖的 jar 包们。spring-boot-loa.."><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 5.4.1"><link rel="alternate" href="/blog/atom.xml" title="janedler's blog" type="application/atom+xml">
</head><body class="is-flex is-flex-direction-column"><header class="header-widget is-flex-shrink-0 is-hidden-mobile"><div class="container is-fullhd is-flex is-justify-content-space-between is-align-items-center is-full-height"><section class="is-hidden-mobile is-flex-shrink-0"><h2><a href="/blog/">田园牧歌(*︶*)'s blog</a></h2></section><h3 class="is-hidden-mobile is-family-serif is-full-height is-flex is-align-items-center is-flex-shrink-0"><div class="is-full-height" id="postTopic"><p class="is-full-height is-flex-shrink-0 is-flex is-align-items-center is-justify-content-center">【SpringBoot源码解析系列】 Jar启动实现</p><p class="is-full-height is-flex-shrink-0 is-flex is-align-items-center is-justify-content-center">点击返回顶部</p></div></h3><aside class="is-flex-shrink-0"><h3 class="is-inline-block"><a href="/blog/">首页</a></h3><h3 class="is-inline-block"><a href="/blog/about">关于</a></h3><h3 class="is-inline-block"><a href="/blog/archives">归档</a></h3></aside></div></header><header class="is-flex header-widget is-flex-shrink-0 is-align-items-center is-justify-content-center is-hidden-tablet"><h3 class="is-inline-block"><a href="/blog/">首页</a></h3><h3 class="is-inline-block"><a href="/blog/about">关于</a></h3><h3 class="is-inline-block"><a href="/blog/archives">归档</a></h3></header><main><main class="container is-max-widescreen content section post-page pt-4 px-4"><div class="columns is-flex-desktop is-justify-content-center is-flex-direction-row-reverse"><div class="column is-3 is-hidden-mobile"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%B8%80%E3%80%81%E6%A6%82%E8%BF%B0"><span class="toc-text">一、概述</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%BA%8C%E3%80%81MANIFEST-MF"><span class="toc-text">二、MANIFEST.MF</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E4%B8%89%E3%80%81JarLauncher"><span class="toc-text">三、JarLauncher</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1%E3%80%81registerUrlProtocolHandler"><span class="toc-text">1、registerUrlProtocolHandler</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E3%80%81createClassLoader"><span class="toc-text">2、createClassLoader</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#2-1%E3%80%81getClassPathArchives"><span class="toc-text">2.1、getClassPathArchives</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#2-2%E3%80%81createClassLoader"><span class="toc-text">2.2、createClassLoader</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3%E3%80%81launch"><span class="toc-text">3、launch</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#3-1%E3%80%81getMainClass"><span class="toc-text">3.1、getMainClass</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#3-2%E3%80%81createMainMethodRunner"><span class="toc-text">3.2、createMainMethodRunner</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4%E3%80%81LaunchedURLClassLoader"><span class="toc-text">4、LaunchedURLClassLoader</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9B%9B%E3%80%81%E6%80%BB%E7%BB%93"><span class="toc-text">四、总结</span></a></li></ol></div><div class="column is-9"><header class="my-4"><a href="/blog/tags/Java"><i class="tag post-item-tag">Java</i></a><a href="/blog/tags/SpringBoot"><i class="tag post-item-tag">SpringBoot</i></a></header><h1 class="mt-0 mb-1 is-family-serif" id="postTitle">【SpringBoot源码解析系列】 Jar启动实现</h1><time class="has-text-grey" datetime="2022-12-13T16:00:00.000Z">2022-12-14</time><article class="mt-2 post-content"><p><img src="https://janedler.oss-cn-beijing.aliyuncs.com/images/springboot.png" alt="cover"></p>
<p>本文主要讲述SpringBoot中Jar启动实现</p>
<span id="more"></span>

<h2 id="一、概述"><a href="#一、概述" class="headerlink" title="一、概述"></a>一、概述</h2><p>Spring Boot 提供了 Maven 插件 <a target="_blank" rel="noopener" href="https://docs.spring.io/spring-boot/docs/current/reference/html/build-tool-plugins.html#build-tool-plugins-maven-plugin"><code>spring-boot-maven-plugin</code></a>，可以方便的将 Spring Boot 项目打成 <code>jar</code>包或者 <code>war</code> 包。<br>Jar包结构图，一共分为4个部分<br><img src="http://www.iocoder.cn/images/Spring-Boot/2019-01-07/01.png" alt="Spring Boot `jar` 包"></p>
<p>① <code>META-INF</code> 目录：通过 <code>MANIFEST.MF</code> 文件提供 <code>jar</code> 包的<strong>元数据</strong>，声明了 <code>jar</code> 的启动类。<br>② <code>org</code> 目录：为 Spring Boot 提供的 <a target="_blank" rel="noopener" href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-tools/spring-boot-loader/"><code>spring-boot-loader</code></a> 项目，它是 <code>java -jar</code> 启动 Spring Boot 项目的秘密所在，也是稍后我们将深入了解的部分。<br>③ <code>BOOT-INF/lib</code> 目录：我们 Spring Boot 项目中引入的<strong>依赖</strong>的 <code>jar</code> 包们。<code>spring-boot-loader</code> 项目很大的一个作用，就是<strong>解决 <code>jar</code> 包里嵌套 <code>jar</code> 的情况</strong>，如何加载到其中的类。<br>④ <code>BOOT-INF/classes</code> 目录：我们在 Spring Boot 项目中 Java 类所编译的 <code>.class</code>、配置文件等等。</p>
<h2 id="二、MANIFEST-MF"><a href="#二、MANIFEST-MF" class="headerlink" title="二、MANIFEST.MF"></a>二、MANIFEST.MF</h2><pre><code class="bash">Manifest-Version: 1.0
Implementation-Title: lab-39-demo
Implementation-Version: 2.2.2.RELEASE
Start-Class: cn.iocoder.springboot.lab39.skywalkingdemo.Application
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.2.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher
</code></pre>
<p>它实际是一个 <strong>Properties</strong> 配置文件，每一行都是一个配置项目。重点来看看两个配置项：</p>
<ul>
<li><code>Main-Class</code> 配置项：Java 规定的 <code>jar</code> 包的启动类，这里设置为 <code>spring-boot-loader</code> 项目的 <a target="_blank" rel="noopener" href="https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java">JarLauncher</a> 类，进行 Spring Boot 应用的启动。</li>
<li><code>Start-Class</code> 配置项：Spring Boot 规定的<strong>主</strong>启动类，这里设置为我们定义的 Application 类。</li>
</ul>
<h2 id="三、JarLauncher"><a href="#三、JarLauncher" class="headerlink" title="三、JarLauncher"></a>三、JarLauncher</h2><p><code>JarLauncher</code> 类是针对 Spring Boot <code>jar</code> 包的启动类<br><code>WarLancher</code>类是针对Spring Boot <code>war</code>包的启动类<br>整体类图如下所示<br><img src="http://www.iocoder.cn/images/Spring-Boot/2019-01-07/11.png" alt="JarLauncher 类图"></p>
<pre><code class="java">public class JarLauncher extends ExecutableArchiveLauncher &#123;

   static final String BOOT_INF_CLASSES = &quot;BOOT-INF/classes/&quot;;

   static final String BOOT_INF_LIB = &quot;BOOT-INF/lib/&quot;;

   public JarLauncher() &#123;
   &#125;

   protected JarLauncher(Archive archive) &#123;
      super(archive);
   &#125;

   @Override
   protected boolean isNestedArchive(Archive.Entry entry) &#123;
      if (entry.isDirectory()) &#123;
         return entry.getName().equals(BOOT_INF_CLASSES);
      &#125;
      return entry.getName().startsWith(BOOT_INF_LIB);
   &#125;

   public static void main(String[] args) throws Exception &#123;
      new JarLauncher().launch(args);
   &#125;

&#125;
</code></pre>
<p>通过 <code>#main(String[] args)</code> 方法，创建 JarLauncher 对象，并调用其 <code>#launch(String[] args)</code> 方法进行启动。整体的启动逻辑，其实是由父类 <code>Launcher</code> 所提供，如下图所示：</p>
<pre><code class="java">JarLauncher#main ---&gt; Laucher#launch ---&gt; MainMethodRunner#run
通过反射调用main
Class&lt;?&gt; mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod(&quot;main&quot;, String[].class);
mainMethod.invoke(null, new Object[] &#123; this.args &#125;);
</code></pre>
<p>父类 Launcher 的 <code>#launch(String[] args)</code> 方法，代码如下：</p>
<pre><code class="java">protected void launch(String[] args) throws Exception &#123;
   // &lt;1&gt; 注册 URL 协议的处理器
   JarFile.registerUrlProtocolHandler();
   // &lt;2&gt; 创建类加载器
   ClassLoader classLoader = createClassLoader(getClassPathArchives());
   // &lt;3&gt; 执行启动类的 main 方法
   launch(args, getMainClass(), classLoader);
&#125;
</code></pre>
<ul>
<li><code>&lt;1&gt;</code> 处，调用 JarFile 的 <code>#registerUrlProtocolHandler()</code> 方法，注册 Spring Boot 自定义的 <code>URLStreamHandler</code> 实现类，用于 <code>jar</code> 包的加载读取。</li>
<li><code>&lt;2&gt;</code> 处，调用自身的 <code>#createClassLoader(List&lt;Archive&gt; archives)</code> 方法，创建自定义的 <code>ClassLoader</code> 实现类，用于从 <code>jar</code> 包中加载类。</li>
<li><code>&lt;3&gt;</code> 处，执行我们声明的 Spring Boot 启动类，进行 Spring Boot 应用的启动。</li>
</ul>
<p>简单来说，就是整一个可以读取 <code>jar</code> 包中类的加载器，保证 <code>BOOT-INF/lib</code> 目录下的类和 <code>BOOT-classes</code> 内嵌的 <code>jar</code> 中的类能够被正常加载到，之后执行 Spring Boot 应用的启动。</p>
<h3 id="1、registerUrlProtocolHandler"><a href="#1、registerUrlProtocolHandler" class="headerlink" title="1、registerUrlProtocolHandler"></a>1、registerUrlProtocolHandler</h3><pre><code class="java">#JarFile.java

/**
 * Register a &#123;@literal &#39;java.protocol.handler.pkgs&#39;&#125; property so that a
 * &#123;@link URLStreamHandler&#125; will be located to deal with jar URLs
 */
//通过将 org.springframework.boot.loader 包设置到 &quot;java.protocol.handler.pkgs&quot; 环境变量，
//从而使用到自定义的 URLStreamHandler 实现类 Handler，处理 jar: 协议的 URL
public static void registerUrlProtocolHandler() &#123;
   //获得URLStreamHandler的路径 java.protocol.handler.pkgs
   String handlers = System.getProperty(PROTOCOL_HANDLER, &quot;&quot;);
   //将SpringBoot自定义的HANDLERS_PACKAGE(org.springframework.boot.loader)补充上去
   System.setProperty(PROTOCOL_HANDLER, (&quot;&quot;.equals(handlers) ? HANDLERS_PACKAGE
         : handlers + &quot;|&quot; + HANDLERS_PACKAGE));
   //重置已缓存的URLStreamHandler处理器
   resetCachedUrlHandlers();
&#125;

//重置 URL 中的 URLStreamHandler 的缓存，防止 `jar://` 协议对应的 URLStreamHandler 已经创建
//我们通过设置 URLStreamHandlerFactory 为 null 的方式，清空 URL 中的该缓存。
private static void resetCachedUrlHandlers() &#123;
    try &#123;
        URL.setURLStreamHandlerFactory(null);
    &#125;
    catch (Error ex) &#123;
            // Ignore
    &#125;
&#125;
</code></pre>
<h3 id="2、createClassLoader"><a href="#2、createClassLoader" class="headerlink" title="2、createClassLoader"></a>2、createClassLoader</h3><h4 id="2-1、getClassPathArchives"><a href="#2-1、getClassPathArchives" class="headerlink" title="2.1、getClassPathArchives"></a>2.1、getClassPathArchives</h4><pre><code class="java">ExcutebleArchiveLauncher.java

//Archive对象，先可以暂时理解成一个一个的档案
private final Archive archive;

@Override
protected List&lt;Archive&gt; getClassPathArchives() throws Exception &#123;
   // &lt;1&gt; 获得所有 Archive
   List&lt;Archive&gt; archives = new ArrayList&lt;&gt;(
         this.archive.getNestedArchives(this::isNestedArchive));
   // &lt;2&gt; 后续处理
   postProcessClassPathArchives(archives);
   return archives;
&#125;

protected abstract boolean isNestedArchive(Archive.Entry entry);

protected void postProcessClassPathArchives(List&lt;Archive&gt; archives) throws Exception &#123;&#125;
</code></pre>
<p><code>&lt;1&gt;</code> 处，<code>this::isNestedArchive</code> 代码段，创建了 <code>EntryFilter</code> 匿名实现类，用于过滤 <code>jar</code> 包不需要的目录。这里在它的内部，调用了 <code>#isNestedArchive(Archive.Entry entry)</code> 方法，它是由 JarLauncher 所实现，代码如下:</p>
<pre><code class="java">JarLauncher.java

static final String BOOT_INF_CLASSES = &quot;BOOT-INF/classes/&quot;;

static final String BOOT_INF_LIB = &quot;BOOT-INF/lib/&quot;;

@Override
protected boolean isNestedArchive(Archive.Entry entry) &#123;
   //如果是目录的情况，只要 BOOT-INF/classes/ 目录
   if (entry.isDirectory()) &#123;
      return entry.getName().equals(BOOT_INF_CLASSES);
   &#125;
   // 如果是文件的情况，只要 BOOT-INF/lib/ 目录下的 `jar` 包
   return entry.getName().startsWith(BOOT_INF_LIB);
&#125;
</code></pre>
<p>目的就是过滤获得，<code>BOOT-INF/classes/</code> 目录下的类，以及 <code>BOOT-INF/lib/</code> 的内嵌 <code>jar</code> 包。<br><code>&lt;1&gt;</code> 处，<code>this.archive.getNestedArchives</code> 代码段，调用 Archive 的 <code>#getNestedArchives(EntryFilter filter)</code> 方法，获得 <code>archive</code> 内嵌的 Archive 集合。代码如下：</p>
<pre><code class="java">/**
 * Returns nested &#123;@link Archive&#125;s for entries that match the specified filter.
 * @param filter the filter used to limit entries
 * @return nested archives
 * @throws IOException if nested archives cannot be read
 */
List&lt;Archive&gt; getNestedArchives(EntryFilter filter) throws IOException;
</code></pre>
<p>Archive接口，是 <code>spring-boot-loader</code> 项目定义的<strong>档案</strong>抽象，其子类如下图所示：</p>
<p><img src="https://janedler.oss-cn-beijing.aliyuncs.com/images/202212140944697.png" alt="Archive 类图"></p>
<ul>
<li>ExplodedArchive 是针对<strong>目录</strong>的 Archive 实现类。</li>
<li>JarFileArchive 是针对 <strong><code>jar</code> 包</strong>的 Archive 实现类</li>
</ul>
<p>通过ExecutableArchiveLauncher的构造函数方法获得archive属性。</p>
<pre><code class="java">// ExecutableArchiveLauncher.java

public abstract class ExecutableArchiveLauncher extends Launcher &#123;

    private final Archive archive;

    public ExecutableArchiveLauncher() &#123;
        try &#123;
            this.archive = createArchive();
        &#125; catch (Exception ex) &#123;
            throw new IllegalStateException(ex);
        &#125;
    &#125;

    protected ExecutableArchiveLauncher(Archive archive) &#123;
        this.archive = archive;
    &#125;

    // ... 省略其它
&#125;


// Launcher.java
public abstract class Launcher &#123;

    protected final Archive createArchive() throws Exception &#123;
        // 获得 jar 所在的绝对路径
        ProtectionDomain protectionDomain = getClass().getProtectionDomain();
        CodeSource codeSource = protectionDomain.getCodeSource();
        URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null;
        String path = (location != null) ? location.getSchemeSpecificPart() : null;
        if (path == null) &#123;
            throw new IllegalStateException(&quot;Unable to determine code source archive&quot;);
        &#125;
        File root = new File(path);
        if (!root.exists()) &#123;
            throw new IllegalStateException(
                    &quot;Unable to determine code source archive from &quot; + root);
        &#125;
        // 如果是目录，则使用 ExplodedArchive 进行展开
        // 如果不是目录，则使用 JarFileArchive
        return (root.isDirectory() ? new ExplodedArchive(root)
                : new JarFileArchive(root));
    &#125;

&#125;
</code></pre>
<p><code>BOOT-INF/classes/</code> 目录被归类为<strong>一个</strong> Archive 对象，而 <code>BOOT-INF/lib/</code> 目录下的<strong>每个</strong>内嵌 <code>jar</code> 包都对应<strong>一个</strong> Archive 对象。</p>
<pre><code class="java">@Override
public List&lt;Archive&gt; getNestedArchives(EntryFilter filter) throws IOException &#123;
   List&lt;Archive&gt; nestedArchives = new ArrayList&lt;&gt;();
   //遍历每个Entry,Archive实现了Iterator接口，所以可以这么遍历
   for (Entry entry : this) &#123;
      //匹配过滤
      if (filter.matches(entry)) &#123;
         //创建JarFileArchive
         nestedArchives.add(getNestedArchive(entry));
      &#125;
   &#125;
   return Collections.unmodifiableList(nestedArchives);
&#125;

protected Archive getNestedArchive(Entry entry) throws IOException &#123;
   JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
   if (jarEntry.getComment().startsWith(UNPACK_MARKER)) &#123;
      return getUnpackedNestedArchive(jarEntry);
   &#125;
   try &#123;
      JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
      return new JarFileArchive(jarFile);
   &#125;
   catch (Exception ex) &#123;
      throw new IllegalStateException(
            &quot;Failed to get nested archive for entry &quot; + entry.getName(), ex);
   &#125;
&#125;

private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException &#123;
   String name = jarEntry.getName();
   if (name.lastIndexOf(&#39;/&#39;) != -1) &#123;
      name = name.substring(name.lastIndexOf(&#39;/&#39;) + 1);
   &#125;
   File file = new File(getTempUnpackFolder(), name);
   if (!file.exists() || file.length() != jarEntry.getSize()) &#123;
      unpack(jarEntry, file);
   &#125;
   return new JarFileArchive(file, file.toURI().toURL());
&#125;
</code></pre>
<h4 id="2-2、createClassLoader"><a href="#2-2、createClassLoader" class="headerlink" title="2.2、createClassLoader"></a>2.2、createClassLoader</h4><pre><code class="java">// ExecutableArchiveLauncher.java

protected ClassLoader createClassLoader(List&lt;Archive&gt; archives) throws Exception &#123;
    // 获得所有 Archive 的 URL 地址
    List&lt;URL&gt; urls = new ArrayList&lt;&gt;(archives.size());
    for (Archive archive : archives) &#123;
        urls.add(archive.getUrl());
    &#125;
    // 创建加载这些 URL 的 ClassLoader
    return createClassLoader(urls.toArray(new URL[0]));
&#125;

protected ClassLoader createClassLoader(URL[] urls) throws Exception &#123;
    return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
&#125;
</code></pre>
<p>基于获得的 Archive 数组，创建自定义 ClassLoader 实现类 <code>LaunchedURLClassLoader</code>，通过它来加载 <code>BOOT-INF/classes</code> 目录下的类，以及 <code>BOOT-INF/lib</code> 目录下的 <code>jar</code> 包中的类。</p>
<h3 id="3、launch"><a href="#3、launch" class="headerlink" title="3、launch"></a>3、launch</h3><pre><code class="java">protected void launch(String[] args, String mainClass, ClassLoader classLoader)
      throws Exception &#123;
   //&lt;1&gt; 设置 LaunchedURLClassLoader 作为类加载器
   Thread.currentThread().setContextClassLoader(classLoader);
   // &lt;2&gt; 创建 MainMethodRunner 对象，并执行 run 方法，启动 Spring Boot 应用
   createMainMethodRunner(mainClass, args, classLoader).run();
&#125;
</code></pre>
<p>该方法负责最终的 Spring Boot 应用真正的<strong>启动</strong>。<br><code>&lt;1&gt;</code> 处：设置<code>createClassLoader</code>创建的<code>LaunchedURLClassLoader</code>作为类加载器，从而保证能够从 jar 加载到相应的类。<br><code>&lt;2&gt;</code> 处，调用 #createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) 方法，创建<code>MainMethodRunner</code>对象，并执行其 #run() 方法来启动 Spring Boot 应用。</p>
<h4 id="3-1、getMainClass"><a href="#3-1、getMainClass" class="headerlink" title="3.1、getMainClass"></a>3.1、getMainClass</h4><pre><code class="java">// ExecutableArchiveLauncher.java

@Override
protected String getMainClass() throws Exception &#123;
   //获得启动的类的全名
   Manifest manifest = this.archive.getManifest();
   String mainClass = null;
   if (manifest != null) &#123;
      mainClass = manifest.getMainAttributes().getValue(&quot;Start-Class&quot;);
   &#125;
   if (mainClass == null) &#123;
      throw new IllegalStateException(
            &quot;No &#39;Start-Class&#39; manifest entry specified in &quot; + this);
   &#125;
   return mainClass;
&#125;
</code></pre>
<p>从 <code>jar</code> 包的 <code>MANIFEST.MF</code> 文件的 <code>Start-Class</code> 配置项，，获得我们设置的 Spring Boot 的<strong>主</strong>启动类。</p>
<h4 id="3-2、createMainMethodRunner"><a href="#3-2、createMainMethodRunner" class="headerlink" title="3.2、createMainMethodRunner"></a>3.2、createMainMethodRunner</h4><p>MainMethodRunner类，负责SpringBoot应用的启动。</p>
<pre><code class="java">public class MainMethodRunner &#123;

    private final String mainClassName;

    private final String[] args;

    /**
     * Create a new &#123;@link MainMethodRunner&#125; instance.
     * @param mainClass the main class
     * @param args incoming arguments
     */
    public MainMethodRunner(String mainClass, String[] args) &#123;
        this.mainClassName = mainClass;
        this.args = (args != null) ? args.clone() : null;
    &#125;

    public void run() throws Exception &#123;
        // &lt;1&gt; 加载 Spring Boot
        Class&lt;?&gt; mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
        // &lt;2&gt; 反射调用 main 方法
        Method mainMethod = mainClass.getDeclaredMethod(&quot;main&quot;, String[].class);
        mainMethod.invoke(null, new Object[] &#123; this.args &#125;);
    &#125;

&#125;
</code></pre>
<p><code>&lt;1&gt;</code> 处：通过 LaunchedURLClassLoader 类加载器，加载到我们设置的 Spring Boot 的主启动类。<br><code>&lt;2&gt;</code> 处：通过<strong>反射</strong>调用主启动类的 <code>#main(String[] args)</code> 方法，启动 Spring Boot 应用。</p>
<h3 id="4、LaunchedURLClassLoader"><a href="#4、LaunchedURLClassLoader" class="headerlink" title="4、LaunchedURLClassLoader"></a>4、LaunchedURLClassLoader</h3><p><code>LaunchedURLClassLoader</code>是 <code>spring-boot-loader</code> 项目自定义的<strong>类加载器</strong>，实现对 <code>jar</code> 包中 <code>META-INF/classes</code> 目录下的<strong>类</strong>和 <code>META-INF/lib</code> 内嵌的 <code>jar</code> 包中的<strong>类</strong>的<strong>加载</strong>。</p>
<pre><code class="java">// ExecutableArchiveLauncher.java
protected ClassLoader createClassLoader(List&lt;Archive&gt; archives) throws Exception &#123;    
    // 获得所有 Archive 的 URL 地址    
    List&lt;URL&gt; urls = new ArrayList&lt;&gt;(archives.size());   
    for (Archive archive : archives) &#123;        
        urls.add(archive.getUrl());    
    &#125;    
    // 创建加载这些 URL 的 ClassLoader    
    return createClassLoader(urls.toArray(new URL[0]));
&#125;

protected ClassLoader createClassLoader(URL[] urls) throws Exception &#123;    
    return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
&#125;

// LaunchedURLClassLoader.java
public class LaunchedURLClassLoader extends URLClassLoader &#123;    
    public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) &#123;
        super(urls, parent);    
    &#125;    
&#125;
</code></pre>
<ul>
<li>第一个参数 <code>urls</code>，使用的是 Archive 集合对应的 URL 地址们，从而告诉 LaunchedURLClassLoader 读取 <code>jar</code> 的<strong>地址</strong>。</li>
<li>第二个参数 <code>parent</code>，设置 LaunchedURLClassLoader 的<strong>父</strong>加载器。这里后续胖友可以理解下，类加载器的<strong>双亲委派模型</strong>，这里就拓展开了。</li>
</ul>
<pre><code class="java">@Override
protected Class&lt;?&gt; loadClass(String name, boolean resolve)
      throws ClassNotFoundException &#123;
   Handler.setUseFastConnectionExceptions(true);
   try &#123;
      try &#123;
         definePackageIfNecessary(name);
      &#125;
      catch (IllegalArgumentException ex) &#123;
         // Tolerate race condition due to being parallel capable
         if (getPackage(name) == null) &#123;
            // This should never happen as the IllegalArgumentException indicates
            // that the package has already been defined and, therefore,
            // getPackage(name) should not return null.
            throw new AssertionError(&quot;Package &quot; + name + &quot; has already been &quot;
                  + &quot;defined but it could not be found&quot;);
         &#125;
      &#125;
      return super.loadClass(name, resolve);
   &#125;
   finally &#123;
      Handler.setUseFastConnectionExceptions(false);
   &#125;
&#125;

/**
 * Define a package before a &#123;@code findClass&#125; call is made. This is necessary to
 * ensure that the appropriate manifest for nested JARs is associated with the
 * package.
 * @param className the class name being found
 */
private void definePackageIfNecessary(String className) &#123;
   int lastDot = className.lastIndexOf(&#39;.&#39;);
   if (lastDot &gt;= 0) &#123;
      String packageName = className.substring(0, lastDot);
      if (getPackage(packageName) == null) &#123;
         try &#123;
            definePackage(className, packageName);
         &#125;
         catch (IllegalArgumentException ex) &#123;
            // Tolerate race condition due to being parallel capable
            if (getPackage(packageName) == null) &#123;
               // This should never happen as the IllegalArgumentException
               // indicates that the package has already been defined and,
               // therefore, getPackage(name) should not have returned null.
               throw new AssertionError(
                     &quot;Package &quot; + packageName + &quot; has already been defined &quot;
                           + &quot;but it could not be found&quot;);
            &#125;
         &#125;
      &#125;
   &#125;
&#125;

private void definePackage(String className, String packageName) &#123;
   try &#123;
      AccessController.doPrivileged((PrivilegedExceptionAction&lt;Object&gt;) () -&gt; &#123;
         String packageEntryName = packageName.replace(&#39;.&#39;, &#39;/&#39;) + &quot;/&quot;;
         String classEntryName = className.replace(&#39;.&#39;, &#39;/&#39;) + &quot;.class&quot;;
         for (URL url : getURLs()) &#123;
            try &#123;
               URLConnection connection = url.openConnection();
               if (connection instanceof JarURLConnection) &#123;
                  JarFile jarFile = ((JarURLConnection) connection)
                        .getJarFile();
                  if (jarFile.getEntry(classEntryName) != null
                        &amp;&amp; jarFile.getEntry(packageEntryName) != null
                        &amp;&amp; jarFile.getManifest() != null) &#123;
                     definePackage(packageName, jarFile.getManifest(), url);
                     return null;
                  &#125;
               &#125;
            &#125;
            catch (IOException ex) &#123;
               // Ignore
            &#125;
         &#125;
         return null;
      &#125;, AccessController.getContext());
   &#125;
   catch (java.security.PrivilegedActionException ex) &#123;
      // Ignore
   &#125;
&#125;
</code></pre>
<p><code>&lt;1&gt;</code> 处，在通过<strong>父类</strong>的 <code>#getPackage(String name)</code> 方法获取不到指定类所在的包时，<strong>会通过遍历 <code>urls</code> 数组，从 <code>jar</code> 包中加载类所在的包</strong>。当找到包时，会调用 <code>#definePackage(String name, Manifest man, URL url)</code> 方法，设置包所在的 <strong>Archive</strong> 对应的 <code>url</code>。<br><code>&lt;2&gt;</code> 处，调用<strong>父类</strong>的 <code>#loadClass(String name, boolean resolve)</code> 方法，加载对应的类。</p>
<h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><p><img src="https://janedler.oss-cn-beijing.aliyuncs.com/images/202212141128448.png" alt="Spring Boot `jar` 启动原理"></p>
<p><strong>红色</strong>部分，解决 <code>jar</code> 包中的<strong>类加载</strong>问题：</p>
<ul>
<li>通过<code>Archive</code>，实现 <code>jar</code> 包的<strong>遍历</strong>，将 <code>META-INF/classes</code> 目录和 <code>META-INF/lib</code> 的每一个内嵌的 <code>jar</code> 解析成一个 Archive 对象。</li>
<li>通过 [Handler<code>，处理 </code>jar:` 协议的 URL 的资源<strong>读取</strong>，也就是读取了每个 Archive 里的内容。</li>
<li>通过<code>LaunchedURLClassLoader</code>，实现 <code>META-INF/classes</code> 目录下的类和 <code>META-INF/classes</code> 目录下内嵌的 <code>jar</code> 包中的类的加载。具体的 URL 来源，是通过 Archive 提供；具体 URL 的读取，是通过 Handler 提供。</li>
</ul>
<p><strong>橘色</strong>部分，解决 Spring Boot 应用的<strong>启动</strong>问题：</p>
<ul>
<li>通过 <code>MainMethodRunner</code> ，实现 Spring Boot 应用的启动类的执行。</li>
</ul>
<p>当然，上述的一切都是通过<code>Launcher</code>来完成引导和启动，通过 <code>MANIFEST.MF</code> 进行具体配置。</p>
</article><section class="jump-container is-flex is-justify-content-space-between my-6"><!-- em is empty placeholder--><em></em><a class="button is-default" href="/blog/2022/12/10/Spring/Spring-AOP%E5%88%9D%E4%BD%93%E9%AA%8C/" title="【Spring源码解析系列】 AOP初体验"><span class="has-text-weight-semibold">下一页: 【Spring源码解析系列】 AOP初体验</span><i class="iconfont icon-next ml-2 has-text-grey"></i></a></section><article class="mt-6 comment-container"><script async repo="janedler/blog" src="https://utteranc.es/client.js" issue-term="pathname" theme="preferred-color-scheme"></script></article></div></div></main></main><footer class="is-flex is-flex-direction-column is-align-items-center is-flex-shrink-0 is-family-serif"><section class="sns-container"><!-- Github--><a title="github" target="_blank" rel="noopener nofollow" href="//github.com//janedler"><i class="iconfont icon-github"></i></a><!-- Ins--><!-- RSS--><a title="rss" target="_blank" rel="noopener nofollow" href="/blog/atom.xml"><i class="iconfont icon-rss"></i></a><!-- 知乎--><!-- 领英--><!-- 脸书--></section><p><span>Copyright ©</span><span> 田园牧歌(*︶*) 2022</span></p><div class="is-flex is-justify-content-center is-flex-wrap-wrap"><p>Powered by Hexo &verbar;&nbsp;</p><p class="is-flex is-justify-content-center"><a title="Hexo theme author" target="_blank" rel="noopener" href="//github.com/haojen">Theme by Haojen&nbsp;</a></p><div style="margin-top: 2px"><a class="github-button" title="github-button" target="_blank" rel="noopener" href="https://github.com/haojen/hexo-theme-Claudia" data-color-scheme="no-preference: light; light: light; dark: dark;" data-show-count="true"></a></div></div><div><span></span></div></footer><script async defer src="https://buttons.github.io/buttons.js"></script><script src="/blog/js/post.js"></script></body></html>